本文为学习Möller-Trumbore算法的笔记,这里不对前面重心坐标求交进行记录,较为简单可自行推导。
根据中心坐标系可以很轻松的得出
P=(1−u−v)A+uB+vC
这里P为射线与三角形相交点。u, v为以三角形两边为坐标系轴的P的投影长度(也可以理解为是P与该边两点连线三角形的面积与整体之比)。
P=A−uA−vA+uB+vC=A+u(B−A)+v(C−A)
而P=O+tD,因此代入可得
O+tDO−A==A+u(B−A)+v(C−A)−tD+u(B−A)+v(C−A)
这里转换为线性方程组的形式
[−D(B−A)(C−A)]⎣⎡tuv⎦⎤=O−A
那么问题就转化为如何求解出这里的t u v三个分量
运用克莱姆法则
令M = [−D(B−A)(C−A)], 也就是将上式的O−A作为替换项,依次替换M中的−D,B−A, C−A,得到Mt,Mu,Mv。那么最终t=MMt,u=MMu,v=MMv。理所当然的可以写为下面这类形式
⎣⎡tuv⎦⎤=[∣∣−D(B−A)(C−A)∣∣]1⎣⎡∣∣O−AB−AC−A∣∣∣∣−DO−AC−A∣∣∣∣−DB−AO−A∣∣⎦⎤
原文在这里使用了变量替换,本质一致
Scalar triple product很好理解,本质上就是计算一个平行六面体的体积,无论是先进行何方向上的叉积点积,最终计算的都是平行六面体的体积。
纯量三重积最重要的一点就是A.(B×C)=det⎣⎡a1a2a3b1b2b3c1c2c3⎦⎤
D=B×C=[bycz−bzcybzcx−bxczbxcy−bycx]
那么
A.D=[axayaz].D=axbycz+aybzcx+azbxcy−azbycx−aybxcz−axbzcy
对比行列式结果与上式,发现一致。
根据纯量三重积的结论,可以推出
⎣⎡tuv⎦⎤=(D×(C−A))⋅(B−A)1⎣⎡((O−A)×(B−A))⋅(C−A)(D×(C−A))⋅(O−A)((O−A)×(B−A))⋅D⎦⎤
注意这里已经通过交换叉积顺序抵消−D前方的负号了
最终实现代码
bool rayTriangleIntersect(
const Vec3f &orig, const Vec3f &dir,
const Vec3f &v0, const Vec3f &v1, const Vec3f &v2,
float &t, float &u, float &v)
{
// ---1
Vec3f v0v1 = v1 - v0;
Vec3f v0v2 = v2 - v0;
Vec3f pvec = dir.crossProduct(v0v2);
float det = v0v1.dotProduct(pvec);
// if the determinant is negative the triangle is backfacing
// if the determinant is close to 0, the ray misses the triangle
if (det < kEpsilon) return false;
float invDet = 1 / det;
// ---2
Vec3f tvec = orig - v0;
u = tvec.dotProduct(pvec) * invDet;
if (u < 0 || u > 1) return false;
// ---3
Vec3f qvec = tvec.crossProduct(v0v1);
v = dir.dotProduct(qvec) * invDet;
if (v < 0 || u + v > 1) return false;
// ---4
t = v0v2.dotProduct(qvec) * invDet;
return true;
#else
...
#endif
}
这里第一步先行计算(D×(C−A))⋅(B−A)1中的分母是否为0,来判断三角形平面与射线方向是否平行。从分母的几何意义上说,根据交换律本质上原式也等价于D.((C−A)×(B−A)),而D的右项即为法线(具体看左右手系)
我这里选取右手系,因为计算得到的法线为反的,因此这里在判断det的正负时要看具体左右手系。
得到了det之后就按照克莱姆法则依次计算出t, u, v即可。先行判断u,v的取值范围就是用于判断相交点P是否在三角形内。